package com.vitas.coin.task;

import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.NonNull;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;

public class TaskEngine implements IEngine {
    private static final String TAG = "TaskEngine";

    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = CPU_COUNT * 2;
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 3 + 1;
    private static final int KEEP_ALIVE = 60;

    private final BlockingQueue<Runnable> mPoolWorkQueue;

    private ThreadPoolExecutor mDefaultExecutor;

    private static TaskEngine mSingleton;

    private TaskEngine() {
        this.mPoolWorkQueue = new MyLinkedBlockQueue<>();
        init();
    }

    private void init() {
        sThreadFactory.reset();
        this.mDefaultExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                TimeUnit.SECONDS, this.mPoolWorkQueue, sThreadFactory);
        mDefaultExecutor.setRejectedExecutionHandler(new RejectedExecutionHandler() {
            @Override
            public void rejectedExecution(Runnable runnable, ThreadPoolExecutor threadPoolExecutor) {
                if (!threadPoolExecutor.isShutdown()) {
                    threadPoolExecutor.submit(runnable);
                }
            }
        });
    }

    public static TaskEngine getInstance() {
        if (mSingleton == null) {
            synchronized (TaskEngine.class) {
                if (mSingleton == null) {
                    mSingleton = new TaskEngine();
                }
            }
        }
        return mSingleton;
    }

    @Override
    public ITask submit(ITask task) {
        try {
            if (mDefaultExecutor == null
                    || mDefaultExecutor.isShutdown()
                    || mDefaultExecutor.isTerminating()
                    || mDefaultExecutor.isTerminated()) {
                init();
            }
            print(mDefaultExecutor.toString());
            String name = TextUtils.isEmpty(task.getName()) ? task.toString() : task.getName();
            if (task.getTimeout() <= 0) {
                print(name + " submit");
                Future future = mDefaultExecutor.submit(task);
                task.setFuture(future);
                return task;
            }
            Runnable run = new Runnable() {
                @Override
                public void run() {
                    print(name + " start");
                    Future futureTask = mDefaultExecutor.submit(task);
                    task.setFuture(futureTask);
                    try {
                        if (task.getTimeout() > 0) {
                            futureTask.get(task.getTimeout(), TimeUnit.MILLISECONDS);
                        }
                    } catch (Throwable e) {
                        futureTask.cancel(true);
                        print(name + " error, " + e);
                        if (e instanceof TimeoutException) {
                            task.onTimeout();
                        }
                        e.printStackTrace();
                    } finally {
                        if (task.getTimeout() > 0) {
                            task.setStatus(ITask.Status.FINISHED);
                        }
                    }
                    print(name + " finish");
                }
            };
            Future future = mDefaultExecutor.submit(run);
            task.setFuture(future);
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return task;
    }

    private void print(String msg) {
        Log.e(TAG, msg);
    }

    @Override
    public void shutdown() {
        try {
            this.mDefaultExecutor.shutdownNow();
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    static class MyThreadFactory implements ThreadFactory {
        private final AtomicLong mCount = new AtomicLong(1);

        public void reset() {
            mCount.set(1);
        }

        @Override
        public Thread newThread(@NonNull Runnable r) {
            return new Thread(r, TAG + " #" + this.mCount.getAndIncrement());
        }
    }

    private static final MyThreadFactory sThreadFactory = new MyThreadFactory();
}
